# Copyright (C) 2023 Open Source Robotics Foundation
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#################################################
# gz_build_tests(TYPE <test_type>
#                 SOURCES <sources>
#                 [LIB_DEPS <library_dependencies>]
#                 [INCLUDE_DIRS <include_dependencies>]
#                 [TEST_LIST <output_var>]
#                 [ENVIRONMENT <environment>])
#
# Build tests for a Gazebo project. Arguments are as follows:
#
# TYPE: Required. Preferably UNIT, INTEGRATION, PERFORMANCE, or REGRESSION.
#
# SOURCES: Required. The names (without the path) of the source files for your
#          tests. Each file will turn into a test.
#
# LIB_DEPS: Optional. Additional library dependencies that every test should
#           link to, not including the library built by this project (it will be
#           linked automatically). gtest and gtest_main will also be linked.
#
# INCLUDE_DIRS: Optional. Additional include directories that should be visible
#               to all the tests of this type.
#
# TEST_LIST: Optional. Provide a variable which will be given the list of the
#            names of the tests generated by this macro. These will also be the
#            names of the targets.
#
# EXCLUDE_PROJECT_LIB: Pass this argument if you do not want your tests to
#                      link to your project's core library. On Windows, this
#                      will also skip the step of copying the runtime library
#                      into your executable's directory.
#
# ENVIRONMENT: Optional. Used to set the ENVIRONMENT property of the tests.
#
macro(gz_build_tests)
  # Define the expected arguments
  set(options SOURCE EXCLUDE_PROJECT_LIB) # NOTE: DO NOT USE "SOURCE", we're adding it here to catch typos
  set(oneValueArgs TYPE TEST_LIST)
  set(multiValueArgs SOURCES LIB_DEPS INCLUDE_DIRS ENVIRONMENT)

  #------------------------------------
  # Parse the arguments
  _gz_cmake_parse_arguments(gz_build_tests "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

  if(NOT gz_build_tests_TYPE)
    # If you have encountered this error, you are probably migrating to the
    # new gz-cmake system. Be sure to also provide a SOURCES argument
    # when calling gz_build_tests.
    message(FATAL_ERROR "Developer error: You must specify a TYPE for your tests!")
  endif()

  if(gz_build_tests_SOURCE)

    # We have encountered cases where someone accidentally passes a SOURCE
    # argument instead of a SOURCES argument into gz_build_tests, and the macro
    # didn't report any problem with it. Adding this warning should make it more
    # clear when that particular typo occurs.
    message(AUTHOR_WARNING
      "Your script has specified SOURCE for gz_build_tests, which is not an "
      "option. Did you mean to specify SOURCES (note the plural)?")

  endif()

  set(TEST_TYPE ${gz_build_tests_TYPE})

  if(BUILD_TESTING)

    if(NOT DEFINED gz_build_tests_SOURCES)
      message(STATUS "No tests have been specified for ${TEST_TYPE}")
    else()
      list(LENGTH gz_build_tests_SOURCES num_tests)
      message(STATUS "Adding ${num_tests} ${TEST_TYPE} tests")
    endif()

    if(NOT gz_build_tests_EXCLUDE_PROJECT_LIB)
      gz_build_executables(
        PREFIX "${TEST_TYPE}_"
        SOURCES ${gz_build_tests_SOURCES}
        LIB_DEPS gtest gtest_main ${gz_build_tests_LIB_DEPS}
        INCLUDE_DIRS ${gz_build_tests_INCLUDE_DIRS}
        EXEC_LIST test_list)
    else()
      gz_build_executables(
        PREFIX "${TEST_TYPE}_"
        SOURCES ${gz_build_tests_SOURCES}
        LIB_DEPS gtest gtest_main ${gz_build_tests_LIB_DEPS}
        INCLUDE_DIRS ${gz_build_tests_INCLUDE_DIRS}
        EXEC_LIST test_list
        EXCLUDE_PROJECT_LIB)
    endif()

    if(gz_build_tests_TEST_LIST)
      set(${gz_build_tests_TEST_LIST} ${test_list})
    endif()

    # Find the Python interpreter for running the
    # check_test_ran.py script
    if(NOT Python3_Interpreter_FOUND)
      find_package(Python3 COMPONENTS Interpreter)
    endif()

    # Build all the tests
    foreach(target_name ${test_list})

      if(USE_LOW_MEMORY_TESTS)
        target_compile_options(${target_name} PRIVATE -DUSE_LOW_MEMORY_TESTS=1)
      endif()

      add_test(NAME ${target_name} COMMAND
        ${target_name} --gtest_output=xml:${CMAKE_BINARY_DIR}/test_results/${target_name}.xml)

      if(gz_build_tests_ENVIRONMENT)
        set_property(TEST ${target_name} PROPERTY ENVIRONMENT ${gz_build_tests_ENVIRONMENT})
      endif()

      if(UNIX)
        # gtest requies pthread when compiled on a Unix machine
        target_link_libraries(${target_name} pthread)
      endif()

      target_compile_definitions(${target_name} PRIVATE
        "TESTING_PROJECT_SOURCE_DIR=\"${PROJECT_SOURCE_DIR}\"")

      set_tests_properties(${target_name} PROPERTIES TIMEOUT 240)

      if(Python3_Interpreter_FOUND)
        # Check that the test produced a result and create a failure if it didn't.
        # Guards against crashed and timed out tests.
        add_test(check_${target_name} ${Python3_EXECUTABLE} ${GZ_CMAKE_TOOLS_DIR}/check_test_ran.py
          ${CMAKE_BINARY_DIR}/test_results/${target_name}.xml)
      endif()
    endforeach()

  else()

    message(STATUS "Testing is disabled -- skipping ${TEST_TYPE} tests")

  endif()

endmacro()
